package lovely.gwt.json.rebind;

import java.util.Arrays;

import rocket.generator.rebind.GeneratorContext;
import rocket.generator.rebind.GeneratorHelper;
import rocket.generator.rebind.Visibility;
import rocket.generator.rebind.method.Method;
import rocket.generator.rebind.method.NewMethod;
import rocket.generator.rebind.methodparameter.NewMethodParameter;
import rocket.generator.rebind.type.NewConcreteType;
import rocket.generator.rebind.type.Type;
import rocket.util.client.Checker;


public class JsonSerializerGenerator extends rocket.json.rebind.JsonSerializerGenerator {

	/**
	 * Builds a new Deserializer type but first checks if the given type also
	 * has a super type that needs a deserializer.
	 * 
	 * @param type
	 *            The type passed to GWT.create()
	 * @param newTypeName
	 *            The name of the new type being generated
	 */
	@Override
	protected NewConcreteType assembleNewType(final Type type, final String newTypeName) {
		Checker.notNull("parameter:type", type);
		GeneratorHelper.checkJavaTypeName("parameter:newTypeName", newTypeName);

		NewConcreteType deserializer = null;

		final GeneratorContext context = this.getGeneratorContext();
		final Type nativeType = context.getType(lovely.gwt.json.client.JsonSerializable.class.getName());
		if (type.isAssignableTo(nativeType)) {
			// create the new type.
			deserializer = this.createConcreteJsonType(newTypeName, type);
			this.overrideJsonReadObjectMethod(deserializer, type);
			this.addDeserializerSingletonField(deserializer, type);
		} else {
			this.verifyTypeHasNoArgumentsConstructor(type);
	
			// check if super type deserializer exists for type...
			final Type superType = type.getSuperType();
			final Type objectType = context.getObject();
	
			// test if type has a superType and potentially create a serializer for
			// that.
			if (false == superType.equals(objectType)) {
				this.createNewTypeIfNecessary(superType.getName());
			}
			// now that all super type deserializers exist create the new type.
			deserializer = this.createConcreteType(newTypeName, type);
			
			this.overrideReadFieldsMethods(deserializer, type);
			this.overrideReadObjectMethod(deserializer, type);
			
			this.overrideWriteFieldsMethods(deserializer, type);
			this.overrideWriteJsonMethod(deserializer, type);
			
			this.addDeserializerSingletonField(deserializer, type);
		}

		return deserializer;
	}

	/**
	 * Factory method which creates a new ConcreteType awaiting methods and
	 * fields.
	 * 
	 * @param newTypeName
	 * @param type
	 * @return
	 */
	protected NewConcreteType createConcreteJsonType(final String newTypeName, final Type type) {
		Checker.notNull("parameter:type", type);

		final GeneratorContext context = this.getGeneratorContext();
		context.debug("Creating new type \"" + type.getName() + "\".");

		final NewConcreteType newType = context.newConcreteType(newTypeName);
		newType.setAbstract(false);
		newType.setFinal(false);

		// extend JsonSerializerType
		Type superType = type.getSuperType();
		String superTypeName = lovely.gwt.json.client.JsonSerializer.class.getName();
		superType = context.getType(superTypeName);
		newType.setSuperType(superType);
		newType.setVisibility(Visibility.PUBLIC);

		return newType;
	}

	/**
	 * Creates the readObject method and adds it to the given deserializer.
	 * 
	 * @param deserializer
	 *            The deserializer itself.
	 * @param type
	 *            The type the deserializer is being generated for
	 */
	protected void overrideJsonReadObjectMethod(final NewConcreteType deserializer, final Type type) {
		Checker.notNull("parameter:deserializer", deserializer);
		Checker.notNull("parameter:type", type);

		final GeneratorContext context = this.getGeneratorContext();
		context.debug("override " + Constants.READ_COMPLEX_METHOD_NAME + "() for type \"" + type.getName() + "\".");

		final Type jsonSerializer = context.getType(lovely.gwt.json.client.JsonSerializer.class.getName());

		final Method readObject = jsonSerializer.getMethod(Constants.READ_COMPLEX_METHOD_NAME, Arrays.asList(new Type[] { this
				.getJsonValue() }));
		final NewMethod newReadObject = readObject.copy(deserializer);
		newReadObject.setAbstract(false);
		newReadObject.setFinal(false);
		newReadObject.setNative(false);

		final NewMethodParameter jsonValue = (NewMethodParameter) newReadObject.getParameters().get(0);
		jsonValue.setName(Constants.READ_COMPLEX_JSON_VALUE_PARAMETER);
		jsonValue.setFinal(true);

		final ReadJsonTemplatedFile body = new ReadJsonTemplatedFile();
		body.setDeserializerType(type);

		newReadObject.setBody(body);
	}

}
